﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Liquifier;

namespace LiquifierTest
{
    /// <summary>
    ///This is a test class for LiquiferTest and is intended
    ///to contain all LiquiferTest Unit Tests
    ///</summary>
    [TestClass()]
    public partial class LiquiferAttributesTest
    {
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }


        #region Field Exceptions Test

        [Liquifiable(Serialisation.Public | Serialisation.Private, Serialisation.None | Serialisation.Explicit)]
        public class FieldInclusionTestClass
        {
            private string s1;
            protected string s2;
            public string s3;
            internal string s4;

            public string s5 { get; set; }

            protected string s6 { get; set; }

            private string s9 { get; set; }

            [Liquify]
            private string s7 { get; set; }

            internal string s8 { get; set; }

            public void Init()
            {
                s1 = "yes";
                s2 = "yes";
                s3 = "yes";
                s4 = "yes";
                s5 = "yes";
                s6 = "yes";
                s7 = "yes";
                s8 = "yes";
                s9 = "yes";
            }

            public bool Check()
            {
                if (s1 != "yes") return false;
                if (s2 != "yes") return false;
                if (s3 != "yes") return false;
                if (s4 != "yes") return false;

                if (s5 == "yes") return false;

                if (s6 == "yes") return false; //Properties do not have modifiers like fields and therefore private must be explicit
                if (s7 != "yes") return false;
                if (s8 == "yes") return false;
                if (s9 == "yes") return false;

                return true;
            }
        }

        [Liquifiable(Serialisation.None | Serialisation.None, Serialisation.Explicit)]
        public class FieldExclusionTestClass
        {
            public string s1;
            private string s2;
            internal string s3;
            protected string s4;

            public void Init()
            {
                s1 = "yes";
                s2 = "yes";
                s3 = "yes";
                s4 = "yes";
            }

            public void Check()
            {
                Assert.AreEqual(null, s1);
                Assert.AreEqual(null, s2);
                Assert.AreEqual(null, s3);
                Assert.AreEqual(null, s4);
            }

        }

        [TestMethod()]
        public void AreMemberAttributesRespected()
        {
            FieldInclusionTestClass before = new FieldInclusionTestClass();
            before.Init();
            FieldInclusionTestClass after = DoLiquifify(before);
            Assert.IsTrue(after.Check());

            FieldExclusionTestClass before2 = new FieldExclusionTestClass();
            before2.Init();
            FieldExclusionTestClass after2 = DoLiquifify(before2);
            after2.Check();
        }

        #endregion

        //Need to add unit test to check if overridden properties or fields (i.e. new something something) are being processed correctly

        #region Hidden Member Test

        [Liquifiable]
        public class HSuperClass
        {
            [Liquify]
            public string m_one;
            [Liquify]
            public string m_two { get; set; }
            [Liquify]
            public string m_three;
            [Liquify]
            public string m_four { get; set; }
        }

        [Liquifiable]
        public class HSubClass : HSuperClass
        {
            [Liquify]
            new public string m_one;
            [Liquify]
            new public string m_two { get; set; }
            [Liquify]
            new public int m_three;
            [Liquify]
            new public int m_four { get; set; }
        }

        [TestMethod()]
        public void AreHiddenMembersReconstructedCorrectly()
        {
            HSubClass before = new HSubClass();
            before.m_one = "subone";
            before.m_two = "subtwo";
            before.m_three = 3;
            before.m_four = 4;

            HSubClass after = DoLiquifify(before);

            Assert.AreEqual("subone", after.m_one);
            Assert.AreEqual("subtwo", after.m_two);
            Assert.AreEqual(3, after.m_three);
            Assert.AreEqual(4, after.m_four);
        }



        #endregion

        #region Update Existing IList Test

        [Liquifiable]
        public class Contain
        {
            [Liquify]
            public ClassWithIList Parent
            {
                get
                {
                    return parent;
                }
                set
                {
                    parent = value;
                    parent.objects4.Add(this);
                }
            }
            [NonSerialized]
            private ClassWithIList parent;
        }

        [Liquifiable]
        public class ClassWithIList
        {
            public List<object> objects1 = new List<object>();

            [Liquify(SerialisationOptions.UpdateExistingIList)]
            public List<object> objects2 = new List<object>();

            [Liquify(SerialisationOptions.UpdateExistingIList)]
            public ObservableList<object> objects3 = new ObservableList<object>();

            [Liquify(SerialisationOptions.UpdateExistingIList)]
            public ObservableList<Contain> objects4 = new ObservableList<Contain>();

            public ClassWithIList()
            {
                objects1.Add(new object());
                objects2.Add(new object());
                objects3.Add(new object());

                objects3ref = objects3;
            }

            [NonSerialized]
            public ObservableList<object> objects3ref;
        }

        [TestMethod()]
        public void UpdateExistingIListTest()
        {
            ClassWithIList l = new ClassWithIList();

            Contain cc = new Contain() { Parent = l };

            ClassWithIList after = DoLiquifify(l);

            Assert.AreEqual(after.objects1.Count, 1);
            Assert.AreEqual(after.objects2.Count, 2);
            Assert.AreEqual(after.objects3.Count, 2);

            Assert.AreSame(after.objects3, after.objects3ref);

            Assert.AreSame(after.objects4[0].Parent, after);

            //This is not a bug - it is a reminder of a very easy to introduce bug. The serialiser will call Solidify on a Liquifiable object, after the liquifer - and you -
            //have finished constructing it.
            //If you add anything to a list while the list is being constructed (e.g. by deserialising items which add themselves to it) then the liquifier will add
            //them again.
            //Create a Singular Observable list to avoid this.
            Assert.AreEqual(after.objects4.Count, 2); //Set to 1 to see 'bug'
        }

        [TestMethod()]
        public void DoesNonCollidableHashWork()
        {
            List<int> Hashes = new List<int>();

            var flags = GetAllEnums<Serialisation>();

            for (int f = 0; f < flags.Count; f++)
            {
                for (int p = 0; p < flags.Count; p++)
                {
                    LiquifiableAttribute a = new LiquifiableAttribute(flags[f], flags[p]);
                    if (Hashes.Contains(a.GetNonCollidingHashCode()))
                        Assert.Fail();
                    else
                        Hashes.Add(a.GetNonCollidingHashCode());
                }
            }

            Assert.IsTrue(true);
        }

        public static List<T> GetAllEnums<T>()
    where T : struct
        {
            if (typeof(T).BaseType != typeof(Enum)) throw new ArgumentException("T must be an Enum type");

            var values = new List<int>();
            foreach (var e in Enum.GetValues(typeof(T)))
            {
                values.Add((int)e);
            }
            var valuesInverted = values.Select(v => ~v).ToArray();
            var result = new List<T>();
            int max = 0;
            for (int i = 0; i < values.Count; i++)
            {
                max |= values[i];
            }

            for (int i = 0; i <= max; i++)
            {
                int unaccountedBits = i;
                for (int j = 0; j < valuesInverted.Length; j++)
                {
                    unaccountedBits &= valuesInverted[j];
                    if (unaccountedBits == 0)
                    {
                        result.Add((T)(object)i);
                        break;
                    }
                }
            }

            //Check for zero
            try
            {
                if (string.IsNullOrEmpty(Enum.GetName(typeof(T), (T)(object)0)))
                {
                    result.Remove((T)(object)0);
                }
            }
            catch
            {
                result.Remove((T)(object)0);
            }

            return result;
        }

        #endregion
    }
}